package com.literature.search.file.service.impl;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.SecureUtil;
import com.google.common.collect.Maps;
import com.literature.search.core.common.FileStateConstant;
import com.literature.search.core.common.MessageConstant;
import com.literature.search.core.entity.*;
import com.literature.search.core.entity.R.AjaxJson;
import com.literature.search.core.entity.dto.BasePageDTO;
import com.literature.search.core.enums.FileEsStateEnum;
import com.literature.search.core.utils.so.SoMap;
import com.literature.search.core.utils.sp.SpUserUtil;
import com.literature.search.file.service.*;
import com.literature.search.file.task.exception.TaskRunException;
import com.mongodb.client.gridfs.GridFSBucket;
import com.mongodb.client.gridfs.GridFSDownloadStream;
import com.mongodb.client.gridfs.model.GridFSDownloadOptions;
import com.mongodb.client.gridfs.model.GridFSFile;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Field;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.mongodb.gridfs.GridFsResource;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * @Author: DangKong
 * @Date: 2023/3/2 19:59
 * @Description:
 */

@Service
@Slf4j
public class FileServiceImpl implements IFileService {

    public static final String COLLECTION_NAME = "file_document";

    private static final String CONTENT = "content";
    private static final String PAGE_CONTENT_LIST = "page_content_list";
    private static final String PICTURE_PARSE_LIST = "picture_parse_list";

    @Autowired
    private ITaskExecuteService taskExecuteService;


    @Resource
    private MongoTemplate mongoTemplate;  // 用于操作MongoDB

    @Resource
    private GridFsTemplate gridFsTemplate;  // 用于操作GridFS

    @Resource
    private GridFSBucket gridFsBucket;

    @Resource
    private UploadRecordServiceImpl uploadRecordService; // 上传记录

    @Resource
    private RedisService redisService;

    @Resource
    private LikeAndCollectionService likeAndCollectionService;

    @Resource
    private IElasticService elasticService;



    @Override
    public void deleteGridFs(String... id) {
        Query deleteQuery = new Query().addCriteria(Criteria.where("_id").in(id));
        gridFsTemplate.delete(deleteQuery);

    }

    /**
     * 这个方法目前只有构建完索引才使用了
     */
    @Override
    public void updateFile(FileDocument fileDocument) {
        Query query = new Query(Criteria.where("_id").is(fileDocument.getId()));
        Update update = new Update();
        update.set("thumb_id", fileDocument.getThumbId());
        update.set("description", fileDocument.getDescription());
        update.set("page_content_list", fileDocument.getPageContentList());
        update.set("picture_parse_list", fileDocument.getPictureParseList());
        update.set("is_english", fileDocument.getIsEnglish());
        mongoTemplate.updateFirst(query, update, FileDocument.class, COLLECTION_NAME);
    }

    @Override
    public void updateEsState(FileDocument fileDocument, FileEsStateEnum state, String errorMsg) {
        Query query = new Query(Criteria.where("_id").is(fileDocument.getId()));
        if (state != FileEsStateEnum.FAIL) {
            errorMsg = "无";
        }
        Update update = new Update();
        update.set("file_es_state", state);
        update.set("error_msg", errorMsg);
        try {
            mongoTemplate.updateFirst(query, update, FileDocument.class, COLLECTION_NAME);
        } catch (Exception e) {
            log.error("更新文档索引状态信息{}==>出错==>{}", fileDocument, e);
            throw new TaskRunException("更新文档索引状态信息==>出错==>{}", e);
        }


    }
    /**
     * 上传文件到Mongodb的GridFs中
     *
     * @param in          -> InputStream
     * @param contentType -> String
     * @return -> String
     */
    private String uploadFileToGridFs(InputStream in, String contentType) {
        // 使用文件id进行存储
        ObjectId store = gridFsTemplate.store(in, contentType);//存储文件 需要三个参数：输入流、文件名称、文件类型
        //System.out.println("生成的文件id：" + store.toString());
        return store.toString();
    }


    /**
     * 上传文件到Mongodb的GridFs中
     *
     */
    @Override
    public String uploadFileToGridFs(String filePrefix, FileInputStream in, String contentType) {
        String gridfsId = filePrefix + IdUtil.simpleUUID();
        //文件，存储在GridFS中
        ObjectId store = gridFsTemplate.store(in, gridfsId, contentType);
        return store.toString();
    }

    @Override
    public AjaxJson fileUpload(MultipartFile file) {
        User currUser = SpUserUtil.getCurrUser();
        String userId = currUser.getId();
        String userName = currUser.getUserName();

        try {
            if (file != null && !file.isEmpty()) { //文件不为空
                String originFileName = file.getOriginalFilename(); //获取文件名
                if (!StringUtils.hasText(originFileName)) { //文件名为空
                    return AjaxJson.get(MessageConstant.PARAMS_ERROR_CODE, MessageConstant.FORMAT_ERROR);
                }
                //获取文件后缀名
                String suffix = originFileName.substring(originFileName.lastIndexOf(".") + 1);
                //如果后缀名不为pdf
                if (!"pdf".equals(suffix)) {
                    return AjaxJson.get(MessageConstant.PARAMS_ERROR_CODE, MessageConstant.FORMAT_ERROR);
                }
                String fileMd5 = SecureUtil.md5(file.getInputStream()); //获取文件的md5值

                //已存在该文件，则拒绝保存
                FileDocument fileDocumentInDb = getByMd5(fileMd5);
                if (fileDocumentInDb != null) { //已存在该文件
                    return AjaxJson.get(MessageConstant.PARAMS_ERROR_CODE, MessageConstant.DATA_DUPLICATE);
                }
                FileDocument fileDocument = saveToDb(fileMd5, file, userId, userName); //保存到数据库

                // 添加文件上传记录
                UploadRecord uploadRecord = new UploadRecord();
                uploadRecord.setFileId(fileDocument.getId());
                uploadRecord.setFileName(fileDocument.getFileName());
                uploadRecord.setState(FileStateConstant.FILE_UPLOAD_STATUS_UNCHECKED);
                uploadRecord.setUserId(userId);
                uploadRecord.setUserName(fileDocument.getUserName());
                uploadRecord.setUploadDate(fileDocument.getUploadDate());
                uploadRecordService.insertUploadRecord(uploadRecord);


                SoMap map = new SoMap();
                map.put("msg", "文件上传成功,请等待管理员审核");
                map.put("文件ID", fileDocument.getId());
                return AjaxJson.getSuccessData(map);
            } else {
                return AjaxJson.get(MessageConstant.PARAMS_ERROR_CODE, MessageConstant.PARAMS_IS_NOT_NULL);
            }
        } catch (IOException ex) {
            ex.printStackTrace();
            return AjaxJson.get(MessageConstant.PROCESS_ERROR_CODE, MessageConstant.OPERATE_FAILED);
        }
    }


    /**
     * @return java.lang.String
     * @Description 存入数据库及解析索引
     * @Param [fileMd5, file]
     **/
    private FileDocument saveToDb(String md5, MultipartFile file, String userId, String username) {
        FileDocument fileDocument;
        String originFilename = file.getOriginalFilename();
        fileDocument = new FileDocument();
        fileDocument.setFileName(originFilename); //文件名
        fileDocument.setSize(file.getSize());
        fileDocument.setContentType(file.getContentType());
        fileDocument.setUploadDate(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
        fileDocument.setMd5(md5);
        fileDocument.setUserId(userId);
        fileDocument.setUserName(username);

        // 设置文件后缀名
        if (StringUtils.hasText(originFilename)) { //文件名不为空
            String suffix = originFilename.substring(originFilename.lastIndexOf("."));
            fileDocument.setSuffix(suffix);
        }

        try {
            String gridfsId = uploadFileToGridFs(file.getInputStream(), file.getContentType()); //上传到GridFS
            fileDocument.setGridfsId(gridfsId);
            fileDocument = mongoTemplate.save(fileDocument, COLLECTION_NAME);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        // 异步保存数据标签 TODO
        //tagServiceImpl.saveTagWhenSaveDoc(fileDocument);

        return fileDocument;

    }


    @Override
    public AjaxJson rebuildIndex(String fileId) {
        FileDocument fileDocument = queryById(fileId);
        if (fileDocument != null && fileDocument.getFileEsState() != FileEsStateEnum.ON_PROCESS) {
            taskExecuteService.execute(fileDocument);
            return  AjaxJson.getSuccess("重建索引ing");
        } else {
            return AjaxJson.get(MessageConstant.PROCESS_ERROR_CODE, MessageConstant.OPERATE_FAILED);
        }
    }

    @Override
    public FileDocument queryById(String fileId) {
        //根据id 查询文件
        Query query = new Query(Criteria.where("_id").is(fileId));
        // 排除文件内容
        Field field = query.fields();
        field.exclude(CONTENT, PAGE_CONTENT_LIST,PICTURE_PARSE_LIST);
        return mongoTemplate.findOne(query, FileDocument.class, COLLECTION_NAME);

    }
    @Override
    public byte[] getFileThumb(String thumbId) {
        if (StringUtils.hasText(thumbId)) {
            Query gridQuery = new Query().addCriteria(Criteria.where("_id").is(thumbId));
            GridFSFile fsFile = gridFsTemplate.findOne(gridQuery);
            if (fsFile == null){
                return null;
            }
            //开启文件下载
            GridFSDownloadOptions gridFSDownloadOptions = new GridFSDownloadOptions();

            try (GridFSDownloadStream in = gridFsBucket.openDownloadStream(fsFile.getObjectId())) {
                if (in.getGridFSFile().getLength() > 0) {
                    GridFsResource resource = new GridFsResource(fsFile, in); //获取文件流
                    return IoUtil.readBytes(resource.getInputStream());
                } else {
                    return null;
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return null;
    }

    @Override
    public Boolean removeFile(String fileId, boolean isDeleteFile) {
        FileDocument fileDocument = mongoTemplate.findById(fileId, FileDocument.class, COLLECTION_NAME);
        if (fileDocument != null) {
            Query query = new Query().addCriteria(Criteria.where("_id").is(fileId));
            mongoTemplate.remove(query, COLLECTION_NAME);
            if (isDeleteFile) {
                if (fileDocument.getFileEsState() == FileEsStateEnum.SUCCESS){
                    // 删除缩略图和文件
                    List<PageContent> pageContentList = fileDocument.getPageContentList();
                    List<PictureParse> pictureParseList = fileDocument.getPictureParseList();
                    ArrayList<String> fileIdList = new ArrayList<>();
                    if (pageContentList != null && pageContentList.size() > 0) {
                        for (PageContent pageContent : pageContentList) {
                            fileIdList.add(pageContent.getThumbId());
                        }
                    }
                    if (pictureParseList != null && pictureParseList.size() > 0) {
                        for (PictureParse pictureParse : pictureParseList) {
                            fileIdList.add(pictureParse.getGridfsId());
                        }
                    }
                    fileIdList.add(fileDocument.getGridfsId());
                    deleteGridFs(String.valueOf(fileIdList));
                }
                //删除文件
                Query deleteQuery = new Query().addCriteria(Criteria.where("_id").is(fileDocument.getGridfsId()));
                gridFsTemplate.delete(deleteQuery);

            }

            //TODO 删除ES数据

            return true;
        }
        return false;
    }

    @Override
    public List<FileDocument> queryAndUpdate(String... fileId) {
        if (CollectionUtils.isEmpty(Arrays.asList(fileId))) {
            return Collections.emptyList();
        }
        Query query = new Query(Criteria.where("_id").in(fileId));
        Update update = new Update();
        update.set("reviewing", false);
        mongoTemplate.updateMulti(query, update, COLLECTION_NAME);
        return mongoTemplate.find(query, FileDocument.class, COLLECTION_NAME);
    }

    @Override
    public AjaxJson queryFileDocumentResult(BasePageDTO pageDTO, boolean reviewing) {
        Query query = new Query().with(Sort.by(Sort.Direction.DESC, "upload_date"));
        query.addCriteria(Criteria.where("reviewing").is(reviewing));
        Map<String, Object> result = Maps.newHashMap();
        result.put("data", queryFileDocument(pageDTO, reviewing));
        result.put("total", mongoTemplate.count(query, FileDocument.class, COLLECTION_NAME));
        return  AjaxJson.getSuccessData(result);
    }

    @Override
    public List<FileDocument> queryFileDocument(BasePageDTO pageDTO, boolean reviewing) {

        Query query = new Query().with(Sort.by(Sort.Direction.DESC, "upload_date"));
        query.addCriteria(Criteria.where("reviewing").is(reviewing));
        int pageIndex = pageDTO.getPage();
        int pageSize = pageDTO.getRows();
        long skip = (long) (pageIndex - 1) * pageSize;
        query.skip(skip);
        query.limit(pageSize);
        Field field = query.fields();
        field.exclude(CONTENT, PAGE_CONTENT_LIST,PICTURE_PARSE_LIST); // 排除文件内容
        return mongoTemplate.find(query, FileDocument.class, COLLECTION_NAME);
    }

    /**
     * 查询附件
     */
    @Override
    public Optional<FileDocument> getById(String id) {
        FileDocument fileDocument = mongoTemplate.findById(id, FileDocument.class, COLLECTION_NAME);
        if (fileDocument != null) { // 文件存在
            GridFSFile fsFile = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(fileDocument.getGridfsId())));
            if (fsFile == null) {
                return Optional.empty();
            } else {
                fsFile.getObjectId();
            }
            // 开启文件下载
            GridFSDownloadOptions gridFSDownloadOptions = new GridFSDownloadOptions();

            try (GridFSDownloadStream in = gridFsBucket.openDownloadStream(fsFile.getObjectId())) {
                if (in.getGridFSFile().getLength() > 0) { // 文件存在
                    GridFsResource resource = new GridFsResource(fsFile, in); // 获取文件流
                    fileDocument.setContent(IoUtil.readBytes(resource.getInputStream())); // 读取文件流
                    return Optional.of(fileDocument); // 返回文件
                } else {
                    return Optional.empty();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return Optional.empty();
    }

    @Override
    public AjaxJson listFilesByPage(BasePageDTO pageParams) {
        Query query = new Query().with(Sort.by(Sort.Direction.DESC, "upload_date"));
        //page 页数默认为1
        int pageIndex = pageParams.getPage();
        int pageSize = pageParams.getRows();
        long skip = (long) (pageIndex - 1) * pageSize;
        query.skip(skip);
        query.limit(pageSize);
        Field field = query.fields();
        field.exclude(CONTENT, PAGE_CONTENT_LIST,PICTURE_PARSE_LIST); // 排除文件内容

        List<FileDocument> fileDocumentList = mongoTemplate.find(query, FileDocument.class, COLLECTION_NAME);

        return AjaxJson.getSuccessData(fileDocumentList) ;

    }

    /**
     * 根据md5获取文件对象
     *
     * @param md5 String
     * @return -> FileDocument
     */
    @Override
    public FileDocument getByMd5(String md5) {
        if (md5 == null) {
            return null;
        }
        Query query = new Query().addCriteria(Criteria.where("md5").is(md5));
        return mongoTemplate.findOne(query, FileDocument.class, COLLECTION_NAME);
    }

    @Override
    public byte[] getFileBytes(String gridFsId) {
        if (StringUtils.hasText(gridFsId)) {
            GridFSFile fsFile = gridFsTemplate.findOne(Query.query(Criteria.where("_id").is(gridFsId)));
            if (fsFile == null) {
                return new byte[0];
            }
            try (GridFSDownloadStream in = gridFsBucket.openDownloadStream(fsFile.getObjectId())) { // 打开下载流
                if (in.getGridFSFile().getLength() > 0) { // 判断文件是否存在
                    GridFsResource resource = new GridFsResource(fsFile, in); // 获取文件
                    return IoUtil.readBytes(resource.getInputStream()); // 读取文件
                } else {
                    return new byte[0];
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return new byte[0];
    }

    @Override
    public void updateDownloadCount(){
        List<String> fileIds = redisService.getFileDownloadList();
        fileIds.stream().forEach(fileId->{
            Query query = new Query();
            Update update = new Update();
            query.addCriteria(Criteria.where("_id").is(fileId));
            if (mongoTemplate.exists(query,FileDocument.class,COLLECTION_NAME)) {
                int count = redisService.getFileDownloadCount(fileId);
                update.set("download",count);
                mongoTemplate.updateFirst(query,update,FileDocument.class,COLLECTION_NAME);
            }
        });
    }

    @Override
    public void updateViewCount(){
        List<String> fileIds = redisService.getFileViewList();
        Update update = new Update();
        fileIds.stream().forEach(fileId->{
            Query query = new Query(Criteria.where("_id").is(fileId));
            if (mongoTemplate.exists(query,FileDocument.class,COLLECTION_NAME)) {
                int count = redisService.getFileViewCount(fileId);
                update.set("view",count);
                mongoTemplate.updateFirst(query,update,FileDocument.class,COLLECTION_NAME);
            }
        });
    }

    @Override
    public void updateLikeCount(){
        List<String> fileIds = likeAndCollectionService.getLikedFileIds();
        Update update = new Update();
        fileIds.stream().forEach(fileId->{
            Query query = new Query(Criteria.where("_id").is(fileId));
            if (mongoTemplate.exists(query,FileDocument.class,COLLECTION_NAME)) {
                int count = likeAndCollectionService.getLikedCount(fileId);
                update.set("like",count);
                mongoTemplate.updateFirst(query,update,FileDocument.class,COLLECTION_NAME);
            }
        });
    }

    @Override
    public void updateCollectCount(){
        List<String> fileIds = likeAndCollectionService.getCollectdFileIds();
        Update update = new Update();
        fileIds.stream().forEach(fileId->{
            Query query = new Query(Criteria.where("_id").is(fileId));
            if (mongoTemplate.exists(query,FileDocument.class,COLLECTION_NAME)) {
                int count = likeAndCollectionService.getCollectedCount(fileId);
                update.set("collect",count);
                mongoTemplate.updateFirst(query,update,FileDocument.class,COLLECTION_NAME);
            }
        });
    }

    @Override
    public int updateFileToEs(List<String> ids) {
        Query query = new Query();
        query.addCriteria(Criteria.where("_id").in(ids));
        List<FileDocument> fileDocuments = mongoTemplate.find(query, FileDocument.class, COLLECTION_NAME);
        //更新es中对应文件的点赞、收藏、浏览、下载
        return elasticService.update(fileDocuments);
    }
}
